#include "../UnicastUdpSocket.h"

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

// TODO: move this to general part
#include <string.h>

namespace Greatewall
{
    namespace Network
    {
        SysSockHandler
        UnicastUdpSocket::createUniUdpSock()
        {
            return socket(AF_INET, SOCK_DGRAM, 0);
        }

        bool
        UnicastUdpSocket::send(const char* buf, ssize_t& size, bool non_block)
        {
            if (_mode == SERVER)
            {
                // TODO: Error handling
                return false;
            }

            if (!_inited)
            {
                // TODO: Error handling
                return false;
            }

            return IpSocket::send(buf, size, non_block);
        }

        bool
        UnicastUdpSocket::recv(char*& buf, ssize_t& size, bool non_block)
        {
            if (_mode == CLIENT)
            {
                // TODO: Error handling
                return false;
            }

            if (!_inited)
            {
                // TODO: Error handling
                return false;
            }

            return IpSocket::recv(buf, size, non_block);
        }

        bool
        UnicastUdpSocket::sendTo(const char* buf, ssize_t& size, std::string& host, int port, bool non_block)
        {
            if (_mode != PEER)
            {
                // This is only avalaible for peer to peer
                // TODO: Error handling
                return false;
            }

            if (!_inited)
            {
                // TODO: Error handling
                return false;
            }

            if (_host.empty() || port < 0)
            {
                // TODO: Error handling
                return false;
            }

            if (!buf || (buf && size <= 0))
            {
                // TODO: Error handling
                return false;
            }

            in_addr_t target_ip;
            memset(&target_ip, 0, sizeof(target_ip));

            if (!inet_aton(host.c_str(), (in_addr*)&target_ip))
            {
                // TODO: Error handling
                return false;
            }

            sockaddr_in  target;
            memset(&target_ip, 0, sizeof(target_ip));

            target.sin_family      = AF_INET;
            target.sin_addr.s_addr = target_ip;
            target.sin_port        = htons(port);

            size = ::sendto(_hdl, buf, size, non_block ? MSG_DONTWAIT : 0, (sockaddr*)&target, sizeof(target));

            if (size == -1)
            {
                // TODO: Error handling
                return false;
            }

            return true;
        }

        bool
        UnicastUdpSocket::recvFrom(char*& buf, ssize_t& size, std::string& host, int& port, bool non_block)
        {
            if (_mode != PEER)
            {
                // TODO: Error handling
                return false;
            }

            if (!_inited)
            {
                // TODO: Error handling
                return false;
            }

            if (buf && size <= 0)
            {
                // TODO: Error handling
                return false;
            }

            sockaddr_in source;
            memset(&source, 0, sizeof(source));

            socklen_t   source_socklen = 0;

            char*   rbuf  = buf ? buf  : (char*)aligned_buf;
            ssize_t rsize = buf ? size : sizeof(aligned_buf);

            size = recvfrom(_hdl, rbuf, rsize, non_block ? MSG_DONTWAIT : 0, (sockaddr*)&source, &source_socklen);

            if (size == -1)
            {
                // TODO: Error handling
                return false;
            }

            const char* source_addr = inet_ntoa(*(in_addr*)&(source.sin_addr.s_addr));

            host = source_addr ? source_addr : "";
            port = ntohs(source.sin_port);

            return true;
        }

        bool
        UnicastUdpSocket::initUniUdpSock(std::string& host, short port, UnicastMode mode, std::string netif)
        {
            if (_inited)
            {
                // TODO: Error handling
                return false;
            }

            if (_hdl < 0 && ((_hdl = createUniUdpSock()) < 0))
            {
                // TODO: Error handling
                return false;
            }

            sockaddr_in srv;
            memset(&srv, 0, sizeof(srv));

            switch(mode)
            {
                case SERVER: // Recv
                {
                    if (port <= 0)
                    {
                        // Error handling
                        return false;
                    }

                    int flag_on = 1;

                    if (setsockopt(_hdl, SOL_SOCKET, SO_REUSEADDR, &flag_on, sizeof(int)) == -1)
                    {
                        // Error handling
                        return false;
                    }

                    srv.sin_family      = AF_INET;
                    srv.sin_addr.s_addr = htonl(INADDR_ANY);
                    srv.sin_port        = htons(port);

                    if (bind(_hdl, (sockaddr*)&srv, sizeof(srv)) == -1)
                    {
                        // Error handling
                        return false;
                    }

                    break;
                }
                case CLIENT: // Send (or Recv)
                {
                    if (host.empty() || port < 0)
                    {
                        // TODO: Error handling
                        return false;
                    }

                    int flag_on = 1;

                    if (setsockopt(_hdl, SOL_SOCKET, SO_REUSEADDR, &flag_on, sizeof(int)) == -1)
                    {
                        // TODO: Error handling
                        return false;
                    }

                    in_addr_t srv_addr = -1UL;
                    if (!inet_aton(host.c_str(), (in_addr*)&srv_addr))
                    {
                        // TODO: Error handling
                        return false;
                    }

                    srv.sin_family      = AF_INET;
                    srv.sin_addr.s_addr = srv_addr;
                    srv.sin_port        = htons(port);

                    // Use connect, so that we can use send() instead of sendto
                    if (connect(_hdl, (sockaddr*)&srv, sizeof(srv)) < 0)
                    {
                        // TODO: Error handling
                        return false;
                    }

                    break;
                }
                case PEER:
                {
                    int flag_on = 1;

                    if (setsockopt(_hdl, SOL_SOCKET, SO_REUSEADDR, &flag_on, sizeof(int)) == -1)
                    {
                        // TODO Error handling
                        return false;
                    }

                    break;
                }
            }
            _host  = host;
            _port  = port;
            _mode  = mode;
            _netif = netif;
            _inited= true;

            return true;
        }

        void
        UnicastUdpSocket::close()
        {
            if (_hdl > 0)
            {
                ::close(_hdl);
                _inited = false;
            }
        }
    }
}

